#pragma once

#include <optional>
#include <vector>
#include <string>
#include <fstream>
#include <tuple>
#include <queue>

namespace graphviz {
    struct GraphBase {
        std::vector<std::string> attrList;
        GraphBase &addAttr(std::string attr);
        GraphBase &addLabel(std::string attr);
        void printAttr(std::ostream &os) const;
    };

    struct GraphNode : public GraphBase {
        std::string id;
        GraphNode(size_t idx);
        friend std::ostream &
        operator<<(std::ostream &os, const GraphNode &node) {
            os << node.id;
            if (!node.attrList.empty()) node.printAttr(os);
            os << ";\n";
            return os;
        }
    };

    struct GraphDiEdge : public GraphBase {
        const GraphNode &from, &to;
        GraphDiEdge(const GraphNode &from, const GraphNode &to);
        friend std::ostream &
        operator<<(std::ostream &os, const GraphDiEdge &edge) {
            os << edge.from.id << " -> " << edge.to.id;
            if (!edge.attrList.empty()) edge.printAttr(os);
            os << ";\n";
            return os;
        }
    };
} // namespace graphviz

using VisitResult = std::optional<graphviz::GraphNode>;

class Expr;
class Break;
class Continue;
class For;
class If;
class Return;
class While;
class Call;
class FunctionAST;
class PrototypeAST;
class Bool;
class Char;
class Float;
class Int;
class StringPtr;
class BinaryOp;
class DotAST;
class ArrayPtr;
class Struct;
class Var;

class ASTVisitor {
  protected:
    // std::queue<std::tuple<const Expr *, std::string, std::string>>
    // visitQueue;

  public:
    virtual ~ASTVisitor() = default;

    // virtual ASTVisitor &pushBack(const Expr *expr, std::string label) = 0;
    // virtual void run() = 0;

    // control
    virtual VisitResult visitBreakAST(const Break *expr) = 0;
    virtual VisitResult visitContinueAST(const Continue *expr) = 0;
    virtual VisitResult visitForAST(const For *expr) = 0;
    virtual VisitResult visitIfAST(const If *expr) = 0;
    virtual VisitResult visitReturnAST(const Return *expr) = 0;
    virtual VisitResult visitWhileAST(const While *expr) = 0;

    // function
    virtual VisitResult visitCallAST(const Call *expr) = 0;
    virtual VisitResult visitFunctionAST(const FunctionAST *expr) = 0;
    virtual VisitResult visitPrototypeAST(const PrototypeAST *expr) = 0;

    // literal
    virtual VisitResult visitBoolAST(const Bool *expr) = 0;
    virtual VisitResult visitCharAST(const Char *expr) = 0;
    // virtual VisitResult visitConstAST(Const*expr) = 0;
    virtual VisitResult visitFloatAST(const Float *expr) = 0;
    virtual VisitResult visitIntAST(const Int *expr) = 0;
    virtual VisitResult visitStringPtrAST(const StringPtr *expr) = 0;

    // operator
    virtual VisitResult visitBinaryOpAST(const BinaryOp *expr) = 0;
    virtual VisitResult visitDotAST(const DotAST *expr) = 0;

    // var
    virtual VisitResult visitArrayPtrAST(const ArrayPtr *expr) = 0;
    virtual VisitResult visitStructAST(const Struct *expr) = 0;
    virtual VisitResult visitVarAST(const Var *expr) = 0;

    // virtual VisitResult visitAST(*expr) = 0;
    // virtual VisitResult visitAST(*expr) = 0;
    // virtual VisitResult visitAST(*expr) = 0;
    // virtual VisitResult visitAST(*expr) = 0;
    // virtual VisitResult visitAST(*expr) = 0;
};

class ASTAcceptor {
  public:
    virtual ~ASTAcceptor() = default;
    virtual VisitResult accept(ASTVisitor &visitor) const = 0;
};

namespace graphviz {
    class GraphGenerator final : public ASTVisitor {
      private:
        std::ofstream os;

        // 单例模式
        GraphGenerator(std::string outputFilename);
        ~GraphGenerator();

        // std::string currentParentId;
        // std::string currentEdgeLabel;

      public:
        // 单例模式
        static GraphGenerator &get();

        // virtual GraphGenerator &
        // pushBack(const Expr *expr, std::string label) override;
        // virtual void run() override;

        static size_t nodeCount;
        GraphNode newNode();

        void handleChild(
            const GraphNode &parent, const Expr *son, const std::string &hint);

        // control
        virtual VisitResult visitBreakAST(const Break *expr) override;
        virtual VisitResult visitContinueAST(const Continue *expr) override;
        virtual VisitResult visitForAST(const For *expr) override;
        virtual VisitResult visitIfAST(const If *expr) override;
        virtual VisitResult visitReturnAST(const Return *expr) override;
        virtual VisitResult visitWhileAST(const While *expr) override;

        // function
        virtual VisitResult visitCallAST(const Call *expr) override;
        virtual VisitResult visitFunctionAST(const FunctionAST *expr) override;
        virtual VisitResult
        visitPrototypeAST(const PrototypeAST *expr) override;

        // literal
        virtual VisitResult visitBoolAST(const Bool *expr) override;
        virtual VisitResult visitCharAST(const Char *expr) override;
        // virtual VisitResult visitConstAST(Const*expr) override;
        virtual VisitResult visitFloatAST(const Float *expr) override;
        virtual VisitResult visitIntAST(const Int *expr) override;
        virtual VisitResult visitStringPtrAST(const StringPtr *expr) override;

        // operator
        virtual VisitResult visitBinaryOpAST(const BinaryOp *expr) override;
        virtual VisitResult visitDotAST(const DotAST *expr) override;

        // var
        virtual VisitResult visitArrayPtrAST(const ArrayPtr *expr) override;
        virtual VisitResult visitStructAST(const Struct *expr) override;
        virtual VisitResult visitVarAST(const Var *expr) override;
    };
} // namespace graphviz
